package com.sea.base.ext.view

import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Point
import android.graphics.Rect
import android.view.LayoutInflater
import android.view.View
import android.view.View.OnClickListener
import android.view.ViewGroup
import android.view.ViewParent
import android.view.Window
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.annotation.ColorInt
import androidx.annotation.IdRes
import androidx.annotation.IntRange
import androidx.annotation.LayoutRes
import androidx.annotation.Px
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.ViewCompat
import androidx.core.view.doOnAttach
import androidx.core.view.doOnDetach
import androidx.core.view.doOnLayout
import androidx.core.view.postDelayed
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import com.sea.base.R
import com.sea.base.ext.global.dp
import com.sea.base.ext.global.getCurrentMillis
import com.sea.base.ext.global.saveVbToTag
import com.sea.base.ext.global.throwIfDebug
import com.sea.base.utils.KeyboardUtils
import com.sea.base.utils.ViewBindingHelper
import com.sea.base.utils.touch.TouchUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.isActive

/**
 * View基本拓展
 */

typealias ViewGroupLayoutParams = ViewGroup.LayoutParams
typealias RecyclerViewLayoutParams = RecyclerView.LayoutParams
typealias LinearLayoutLayoutParams = LinearLayout.LayoutParams
typealias FrameLayoutLayoutParams = FrameLayout.LayoutParams
typealias GridLayoutManagerLayoutParams = GridLayoutManager.LayoutParams
typealias ConstraintLayoutLayoutParams = ConstraintLayout.LayoutParams

const val MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT
const val WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT

@Suppress("NOTHING_TO_INLINE")
inline fun ViewGroup.getChildOrNull(index: Int) =
    if (index in 0 until childCount) getChildAt(index) else null

inline val View.layoutInflater: LayoutInflater get() = context.layoutInflate()

/**
 * view inflate的kotlin版
 */
@Suppress("NOTHING_TO_INLINE")
inline fun ViewGroup.inflater(@LayoutRes id: Int, attachToRoot: Boolean = false) = context.inflate(id, this, attachToRoot)

/**
 * 默认没有margin，见[setMarginOtherDefault]
 */
private const val MARGIN_DEFAULT = Int.MIN_VALUE

/**
 * 避免快速点击
 */
@JvmOverloads
inline fun <T : View> T.setOnFastClickListener(
    clickInterval: Long = 300,
    crossinline block: (T) -> Unit
) {
    setOnClickListener(object : OnClickListener {
        var timestamp = 0L
        override fun onClick(v: View) {
            val now = getCurrentMillis()
            if (isClickable && now - timestamp >= clickInterval) {
                block(this@setOnFastClickListener)
            }
            timestamp = now
        }
    })
}

/**
 * view的事件区域拓展，支持跨parent调用，解决一级parent仍然不够的问题
 *
 * 有了它就可以愉快的写margin了，不用再因为点击区域过小而烦恼
 *
 * @param parentDeep 附着在第几层父级（注意自己层级）
 * @param sizePx     四周拓展大小
 */
@JvmOverloads
@Suppress("NOTHING_TO_INLINE")
inline fun View.expandTouchArea(
    @IntRange(from = 1, to = 8) parentDeep: Int = 1,
    sizePx: Int = 10.dp
) {
    TouchUtils.expandTouchArea(this, parentDeep, sizePx)
}

@JvmOverloads
@Suppress("NOTHING_TO_INLINE")
inline fun View.expandTouchArea(
    parentView: View,
    sizePx: Int = 10.dp
) {
    TouchUtils.expandTouchArea(this, parentView, sizePx)
}

/**
 * 拓展在可滑动view之下（没有则拓展到最外层view上）
 */
@JvmOverloads
@Suppress("NOTHING_TO_INLINE")
inline fun View.expandTouchAreaForScrollingView(
    sizePx: Int = 10.dp
) {
    TouchUtils.expandTouchAreaForScrollingView(this, sizePx)
}

@Suppress("NOTHING_TO_INLINE")
inline fun View.removeTouchArea() {
    TouchUtils.removeTouchArea(this)
}

/**
 * 增加了生命周期判断
 * 对比
 * [doOnAttach]： 待可见后一定会执行、view测量前执行
 * [doOnLayout]：Layout后会执行（可见不一定执行）、view测量后执行
 * [postLifecycle]：view测量后执行，view detach后直接丢弃
 */
inline fun View.postLifecycle(crossinline callback: () -> Unit) {
    post {
        if (ViewCompat.isAttachedToWindow(this)) {
            callback.invoke()
        }
    }
}

/**
 * 增加了生命周期判断
 * @return 返回值用于取消[View.removeCallbacks]
 */
inline fun View.postDelayedLifecycle(millis: Long, crossinline callback: () -> Unit): Runnable {
    return postDelayed(millis) {
        if (ViewCompat.isAttachedToWindow(this)) {
            callback.invoke()
        }
    }
}

/**
 * @return 提前取消改成了函数，方便取消
 */
inline fun View.postDelayedLifecycleCanCancel(millis: Long, crossinline callback: () -> Unit): () -> Unit {
    val run = postDelayedLifecycle(millis, callback)
    return { removeCallbacks(run) }
}

/**
 * 加了返回值，可自行remove
 */
inline fun View.doOnDetachCanCancel(crossinline action: (view: View) -> Unit): View.OnAttachStateChangeListener? {
    if (!ViewCompat.isAttachedToWindow(this)) {
        action(this)
        return null
    }
    val detachListener = object : View.OnAttachStateChangeListener {
        override fun onViewAttachedToWindow(view: View) {}

        override fun onViewDetachedFromWindow(view: View) {
            removeOnAttachStateChangeListener(this)
            action(view)
        }
    }
    addOnAttachStateChangeListener(detachListener)
    return detachListener
}

/**
 * 设置padding，不传就是原值
 *
 * 注：start和end会覆盖left和right（xml和margin也是如此）
 */
@Suppress("NOTHING_TO_INLINE")
inline fun View.setPaddingOtherDefault(
    @Px start: Int = paddingStart,
    @Px top: Int = paddingTop,
    @Px end: Int = paddingEnd,
    @Px bottom: Int = paddingBottom
) {
    setPaddingRelative(start, top, end, bottom)
}


/**
 * 设置padding，不传就是原值
 *
 * 注：这里是left和right
 */
@Suppress("NOTHING_TO_INLINE")
inline fun View.setAbsPaddingOtherDefault(
    @Px left: Int = paddingLeft,
    @Px top: Int = paddingTop,
    @Px right: Int = paddingRight,
    @Px bottom: Int = paddingBottom
) {
    setPadding(left, top, right, bottom)
}

@get:Deprecated("这只是个平均值，不推荐使用")
inline var View.paddingHorizontal: Int
    get() = (paddingStart + paddingEnd) / 2
    set(value) {
        setPaddingOtherDefault(start = value, end = value)
    }

@get:Deprecated("这只是个平均值，不推荐使用")
inline var View.paddingVertical: Int
    get() = (paddingTop + paddingBottom) / 2
    set(value) {
        setPaddingOtherDefault(top = value, bottom = value)
    }

/**
 * 设置margin，不传就是原值
 * 注意：必须是MarginLayoutParams
 */
fun View.setMarginOtherDefault(
    @Px start: Int = MARGIN_DEFAULT,
    @Px top: Int = MARGIN_DEFAULT,
    @Px end: Int = MARGIN_DEFAULT,
    @Px bottom: Int = MARGIN_DEFAULT
) {
    val params = layoutParams
    if (params !is ViewGroup.MarginLayoutParams) {
        "不正确的MarginLayoutParams：$params".throwIfDebug()
        return
    }
    if (start != MARGIN_DEFAULT) {
        params.leftMargin = start
        params.marginStart = start
    }
    if (top != MARGIN_DEFAULT) {
        params.topMargin = top
    }
    if (end != MARGIN_DEFAULT) {
        params.rightMargin = end
        params.marginEnd = end
    }
    if (bottom != MARGIN_DEFAULT) {
        params.bottomMargin = bottom
    }
    requestLayout()
}

/**
 * 获取焦点，并清除其他view的焦点
 * @param hideInput true则顺便隐藏键盘
 */
@JvmOverloads
@Suppress("NOTHING_TO_INLINE")
inline fun View.requestFocusAndClearOther(hideInput: Boolean = true) {
    rootView.findFocus()?.let {
        it.clearFocus()
        if (hideInput) {
            KeyboardUtils.hideKeyboard(it)
        }
    }
    isFocusable = true
    requestFocus()
}

/**
 * 跟[drawToBitmap]类似，增加了底色
 */
@JvmOverloads
@Suppress("NOTHING_TO_INLINE")
inline fun View.getBitmap(@ColorInt color: Int = Color.TRANSPARENT): Bitmap {
    val bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    val c = Canvas(bmp)
    c.drawColor(color)
    //如果不设置canvas画布为白色，则生成透明
    layout(0, 0, width, height)
    draw(c)
    requestLayout()
    invalidate()
    return bmp
}

inline val View.realContext: Context get() = this.context.realContext

@Suppress("NOTHING_TO_INLINE")
inline fun View.getLocationInWindow(): Point {
    val ints = IntArray(2)
    this.getLocationInWindow(ints)
    return Point(ints[0], ints[1])
}

@Suppress("NOTHING_TO_INLINE")
inline fun View.getLocationOnScreen(): Point {
    val ints = IntArray(2)
    this.getLocationOnScreen(ints)
    return Point(ints[0], ints[1])
}

@Suppress("NOTHING_TO_INLINE")
inline fun View.getLocalVisibleRect() = Rect().also { this.getLocalVisibleRect(it) }

@Suppress("NOTHING_TO_INLINE")
inline fun View.getGlobalVisibleRect() = Rect().also { this.getGlobalVisibleRect(it) }

/**
 * 根据root找到对应的vb（必须使用[ViewBindingHelper]相关方法或者手动调用[saveVbToTag]才会find到）
 */
@Suppress("NOTHING_TO_INLINE")
inline fun <T : ViewBinding> View.findVbByTag() = rootView.getTypeTag<T>(R.id.tag_view_binding_obj)

/**
 * 键盘显隐监听
 * 目前一个view只能有一个监听，你可以指定多个view进行多监听
 * @param onChangedListener true:键盘弹出，false键盘收起
 */
@Suppress("NOTHING_TO_INLINE")
inline fun View.setOnKeyboardChangedListener(noinline onChangedListener: (Boolean) -> Unit) {
    KeyboardUtils.setOnKeyboardChangedListener(this, onChangedListener)
}

@Suppress("NOTHING_TO_INLINE")
inline fun View.removeOnKeyboardChangedListener() {
    KeyboardUtils.removeOnKeyboardChangedListener(this)
}

inline val View.isKeyboardShowing get() = KeyboardUtils.isKeyboardShowing(this)

inline fun View.doOnKeyboardVisible(crossinline onVisibleListener: () -> Unit) {
    if (!isKeyboardShowing) {
        setOnKeyboardChangedListener {
            if (it) {
                onVisibleListener.invoke()
                removeOnKeyboardChangedListener()
            }
        }
    } else {
        onVisibleListener.invoke()
    }
}

inline fun View.doOnKeyboardHide(crossinline onHideListener: () -> Unit) {
    if (isKeyboardShowing) {
        setOnKeyboardChangedListener {
            if (!it) {
                onHideListener.invoke()
                removeOnKeyboardChangedListener()
            }
        }
    } else {
        onHideListener.invoke()
    }
}

inline val View.parentOrNull get() = this.parent?.asViewGroup()

@Suppress("NOTHING_TO_INLINE")
inline fun ViewParent.asViewGroup() = this as ViewGroup

inline val Window.decorViewGroup get() = this.decorView as ViewGroup

val View.viewScope: CoroutineScope
    get() {
        var scope = getTypeTag<CoroutineScope>(R.id.tag_view_scope)
        if (scope == null || !scope.isActive) {
            scope = MainScope()
            setTag(R.id.tag_view_scope, scope)
            doOnDetach {
                scope.cancel()
            }
        }
        return scope
    }

@Suppress("NOTHING_TO_INLINE")
inline fun <T> View.getTypeTag(@IdRes id: Int): T? = getTag(id) as? T